《Effective Java》第四章:类和接口
类和接口是Java程序设计语言的核心,它们也是Java语言的基本抽象单元。
第13条:使类和成员的可访问性最小化
尽可能使每个类或者成员不被外界访问。
Java
的4种访问级别:
- 私有的
private
:只有在申明该成员的类的内部才可以访问。 - 包级私有的
package-private
:包内的任何类都可以访问这个成员,(这个是默认的访问级别) - 受保护的
protected
:子类可以访问超类的protected
成员,但是还有这个protected
成员类的包内的任何类也可以访问。 - 公有的
public
:任何地方都可以访问这个成员。
如何方法覆盖了超类中的中的一个方法,子类中的访问级别就不允许低于超类的访问级别。
这样可以确保使用超类的实例的地方也可以使用子类的实例。
实例域是不能公有的。
其实就是建议类里面只暴露final
修饰的变量成员,其他变量一律不要暴露。
长度非零的数组总是可变的。
就算你使用static final
关键字将一个数组成员暴露出来,但是这个数组成员的内容还是可以被外部给修改的。你可以使用
- 创建不可变量列表
- 克隆这个对象
1 | /** |
总而言之,你应该始终尽可能的降低可访问性,除了公有static final域的特殊情形之外,公有的类都不应该包含公有域,并且要确保公有static final所引用的对象时不可变的。
第14条:在类中使用访问方法而非公有域
这条其实就是对应上面第13条中的公有域解说那部分,除了不要暴露可变的对象外,还有:
- 如果有成员需要公开,最好使用
getter
和setter
方法。 - 暴露不可变对象的有有域时可以在设置值时加入一些约束条件。
第15条:使可变最小化
不可变类比可变类更加易于设计、实现和使用,它们不容易出错,且更加安全。
成为不可变类,要遵循下面五条规则:
- 不要提供任何会修改对象状态的方法
- 保证类不可被扩展
- 所使用的域都是
final
的 - 所使用的域都是私有的
- 确保对于任何可变组件的互斥访问(如果类具有指向可变对象的域,则必须保证)。
不可变对象的优点:
- 不可变对象只有一种状态,即被创建时的状态。
- 不可变对象本质上是线程安全的,可以被自由的共享。
- 不可变对象还可以共享它们的内部信息。
- 不可变对象为其他对象提供了大量的构件。
不可变类真正唯一的缺点是:对于每个不同的值都需要一个单独的对象(这样创建对象的代价就会很高啊~-_-!!)
为了确保类的不可变性,该类绝对不允许自身被子类化
将类用
final
关键字标记1
2
3
4
5
6/**
* 我肯定不能被继承了
*/
public final class Test {
//ToDo
}将构造设置为
private
访问权限,然后开放一个公有的静态工厂来代替构造器。(256个赞,推荐^_^)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
* 你看我还能被继承嘛?^_^
*/
public class Test {
/**
* 我不让外人调用
* @param str
*/
private Test(String str)
{
//ToDo
}
public static Test valueOf(String str)
{
/**
* 在这里进行构造函数的调用
*/
return new Test(str);
}
还有:
- 坚决不要为每个
get
方法编写一个相应的set
方法。 - 除非令人有信服的理由使会变成非
final
的,否则每个域都是final
(恩,总之多用final
没错)
第16条:组合优于继承
- 这里不推荐继承的主要原因是子类必须跟着其超类改变而改变,因而打破了封装性。。。-_-||
- 不用扩展现有类,而是在新的类里面增加一个私有域,它引用现有类的一个实力。在新类中主要去实现转发功能。
这里我想说如果是使用符合实现相同的功能将大大增加代码量。
第17条:要么为继承而设计并提供文档,要么禁止继承
如果该类允许被继承,则最好:
- 对允许被重写的类方法添加足够的文档说明,如果覆盖了这个类之后会对其他的方法产生怎么样的影响。
- 为了能让子类进入超类的内部工作流中,超类必须通过某种形式提供适当的钩子(钩子),这种形式可以是精心得选择的受保护的类,也可以是受保护的域。
第18条:接口优于抽象类
抽象类只能被单继承,而接口则没有此约束。
使用接口还有以下几个优势:
- 现有的类可以很容易实现被更新,以实现新的接口。
只需要定义一个新的接口添加相关方法的声明,然后在该类中implements
该接口并实现那些方法即可。 - 接口是定义mixin(混合类型)的理想选择。
mixin
是指类除了实现它的基本类型之外,还可以实现这个mixin
类型,以表明它提供了某种可供选择的行为,比如,Comparable
是一个mixin
接口. - 接口允许我们构造非层次接口的类型框架(因为接口可以多继承啊)。
第19条:接口只用于定义类型
接口应该只被用来定义类型,它们不应该被用来导出常量(常量接口是一种不被推荐的用法)。
第20条:类层次优于标签类
什么是标签类?看下面那个类就知道了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33class Figure{
enum Shape{RECTANGLE,CIRCLE};
final Shape shape;//当前这个类的标志
double length;
double width;
double radius;
//表示圆
Figure(double radius){
this.shape=Shape.CIRCLE;
this.radius=radius;
}
//表示矩形
Figure(double length,double width){
this.shape=Shape.RECTANGLE;
this.length=length;
this.width=width;
}
//求面积
double area(){
switch(this.shape)//只能使用switch类判断
{
case RECTANGLE:return length*width;break;
case CIRCLE:return Math.PI*(radius*radius);break;
default:throw AssertionError();
}
}
}
这个类里面其实有求圆的面积和矩形的面积,用一个shape标志类区别,但是这样的类过于冗长,容易出错,并且效率低下(这个一般人都知道吧?)
那如何使用类层次类实现上述功能?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33//类层次的基类
abstract class Figure{
abstract double area();
}
//圆形类
class Circle extends Figure{
final double radius;
Circle(double radius){
this.radius=radius;
}
double area(){
return Math.PI*(radius*radius);
}
}
//矩形类
class Rectangle extends Figure{
final double length;
final double width;
//表示矩形
Rectangle(double length,double width){
this.length=length;
this.width=width;
}
double area(){
return this.length*this.width;
}
}
使用类层次类完成上述功能是不是其结构明显简介了许多,并且它还极其容易扩展:1
2
3
4
5
6
7//正方形
class Square extends Rectangle{
Square(double length,double width){
super(length,width);
}
}
所以,标签类很少有适用的适合,当你想要编写一个包含显式包含标签域的类,就应该考虑使用类层次来代替该类。
第21条:用函数对象表示策略
Java
里面无法适用函数指针、代理,lambda表达式,当Java
需要实现将一个方法作为参数传入方法的功能时往往使用类引用来完成。
它往往是:
1.定义一个有某个特定方法的对象A
。
2.B/C/D…
去继承A实现该特定方法。
3.在方法参数作为传参的功能中去传递对象A
,每次都是执行对象A
的特定方法。
4.在调用该功能方法使用A a=new B/C/D()
这种方法实例化并传入。
注:该方式最有代表性的就是
java.util.Comparator
类的使用。
第22条:优先考虑静态成员类
嵌套类是指被定义在另一个类的内部类,它有四种写法:
- 静态成员类
可以使用外围类名.静态成员类
的形式来进行访问,它可以直接方法外围里面的静态成员。 - 非静态成员类
可以使用外围类的实例名.非静态成员类
的形式来进行访问,它可以方法外围类实例里面的方法。例如Adapter
类。 - 匿名类
匿名类可以直接使用没不需要被声明,但是他不能为instantceof
,无法将他们实例化等。例如Runnabel
、Thread
时直接传递的实例。 局部类
(没见过),它可以在“任何声明局部变量”的地方都可以声明局部类,也遵循同样的作用域规则。每种嵌套类都有自己的用途,但是静态成员类相对来说开销较少,功能交全,比较推荐。
本作品采用[知识共享署名-非商业性使用-相同方式共享 2.5]中国大陆许可协议进行许可,我的博客欢迎复制共享,但在同时,希望保留我的署名权kubiCode,并且,不得用于商业用途。如您有任何疑问或者授权方面的协商,请给我留言。